#include "ax_epoll.h"
#include "ax_hashtable.h"
#include "ax_printf.h"
#include "ax_thread.h"
#include "ax_time.h"
#include "com.h"
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAX_EPOLL_EVENTS 100

struct ax_epoll
{
    int                g_epoll;
    struct epoll_event g_events[MAX_EPOLL_EVENTS];
    ax_hashtable*      g_fds;
    ax_mutex_t*        g_mutex;
};

ax_epoll* ax_epoll_create()
{
    ax_epoll* e = ALLOC(ax_epoll);
    ax_check_and_return_zero(e, NULL);
    e->g_epoll = epoll_create(MAX_EPOLL_EVENTS);
    e->g_fds   = ax_hashtable_create(HT_KEY_CONST | HT_VALUE_CONST);
    e->g_mutex = ax_mutex_create();
    memset(e->g_events, 0, sizeof(e->g_events));
    return e;
}

void ax_epool_destory(ax_epoll* e)
{
    ax_check_and_return(e, NULL);
    ax_hashtable_destory(e->g_fds);
    ax_mutex_destroy(e->g_mutex);
}

/**
 * @brief ax_epoll_register
 * @param fd
 * @return
 *
 * can call many times for one fd
 */
bool ax_epoll_register(ax_epoll* e, ax_fd* fd)
{
    ax_check_and_return_false(e && fd, NULL);
    printf("reginster fd:%d\n", fd->fd);

    if(ax_epoll_is_registered(e, fd))
        return true;

    if(ax_hashtable_size(e->g_fds) >= MAX_EPOLL_EVENTS)
        return false;

    struct epoll_event ev;
    ev.data.ptr  = fd;
    ev.events   = (fd->when & AX_FD_READ) ? (EPOLLIN | EPOLLET) : EPOLLOUT;
    if(-1 == epoll_ctl(e->g_epoll, EPOLL_CTL_ADD, fd->fd, &ev))
    {
        ax_error("epoll_ctl add error!\n");
        return false;
    }

    ax_mutex_lock(e->g_mutex);
    ax_hashtable_insert(e->g_fds, &fd->fd, sizeof(fd->fd), fd, sizeof(fd));
    ax_mutex_unlock(e->g_mutex);
    return true;
}

bool ax_epoll_unregister(ax_epoll* e, ax_fd* fd)
{
    printf("unreginster fd:%d\n", fd->fd);
    if(NULL == fd || -1 == e->g_epoll || -1 == fd->fd)
        return false;

    if(!ax_epoll_is_registered(e, fd))
        return true;

    struct epoll_event ev;
    ev.data.ptr = fd;
    ev.events   = (fd->when & AX_FD_WRITE) ? (EPOLLIN | EPOLLET) : EPOLLOUT;
    if(-1 == epoll_ctl(e->g_epoll, EPOLL_CTL_DEL, fd->fd, &ev))
    {
        ax_error("epoll_ctl del error!\n");
        return false;
    }

    ax_mutex_lock(e->g_mutex);
    ax_hashtable_remove(e->g_fds, ( const char* )&fd->fd, sizeof(fd->fd));
    ax_mutex_unlock(e->g_mutex);
    return true;
}

bool ax_epoll_is_registered(ax_epoll* e, ax_fd* fd)
{
    ax_check_and_return_false(fd, NULL);
    ax_mutex_lock(e->g_mutex);
    bool ret = ax_hashtable_contains(e->g_fds, ( const char* )&fd->fd, sizeof(fd->fd));
    ax_mutex_unlock(e->g_mutex);
    return ret;
}

/**
 * @brief ax_epoll_main
 * @param timout_ms
 * @return
 *
 * can not close fd in other thread!!!
 */
int ax_epoll_main(ax_epoll* e, int timout_ms)
{
    int i;
    int nfds = epoll_wait(e->g_epoll, e->g_events, MAX_EPOLL_EVENTS, timout_ms);
    if(-1 == nfds)
    {
        //ax_error("epoll_wait error\n");
        return 0;
    }

    uint64_t now = ax_timestamp();
    for(i = 0; i < nfds; ++i)
    {
        int    ev = e->g_events[i].events;
        ax_fd* fd = ( ax_fd* )e->g_events[i].data.ptr;

        if(ev & EPOLLERR || ev & EPOLLHUP || (!(ev & EPOLLIN)))
        {
            printf("epoll: fd[%d] invalid!\n", fd->fd);
            ax_epoll_unregister(e, fd);
            ax_fd_close(fd);
            continue;
        }
        if(fd && fd->cb)
        {
            fd->last_active_time = now;
            fd->cb(fd, fd->when);
        }
    }
    return 1;
}
